﻿using codeparser.net.v2.Aspects;
using codeparser.net.v2.ConfigurationItems;
using codeparser.net.v2.Contracts;
using codeparser.net.v2.Exceptions;
using codeparser.net.v2.Lexer;
using codeparser.net.v2.Validators;

using configuration.net;

using System;
using System.Xml;
using System.Xml.XPath;

namespace codeparser.net.v2
{
    /// <summary>
    /// Represents the options for a parser.
    /// </summary>
    public class ParserOptions : IXmlWriter
    {
        /// <summary>
        /// Gets or sets the token validator strategy.
        /// </summary>
        /// <value>The token validator strategy.</value>
        public TokenValidatorStrategy TokenValidatorStrategy
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets whether exceptions of type InvalidCharacterException should be thrown.
        /// </summary>
        /// <value><c>true</c> if exceptions of type InvalidCharacterException should be thrown;
        /// <c>false</c> otherwise.</value>
        public bool ThrowInvalidCharacterExceptions
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets whether exceptions of type InvalidTagException should be thrown.
        /// </summary>
        /// <value><c>true</c> if exceptions of type InvalidTagException should be thrown;
        /// <c>false</c> otherwise.</value>
        public bool ThrowInvalidTagExceptions
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets whether exceptions of type InvalidAttributeException should be thrown.
        /// </summary>
        /// <value><c>true</c> if exceptions of type InvalidAttributeException should be thrown;
        /// <c>false</c> otherwise.</value>
        public bool ThrowInvalidAttributeExceptions
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets whether exceptions of type InvalidTagNestingException should be thrown.
        /// </summary>
        /// <value><c>true</c> if exceptions of type InvalidTagNestingException should be thrown;
        /// <c>false</c> otherwise.</value>
        public bool ThrowInvalidTagNestingExceptions
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets whether exceptions of type MissingOpeningTagException should be thrown.
        /// </summary>
        /// <value><c>true</c> if exceptions of type MissingOpeningTagException should be thrown;
        /// <c>false</c> otherwise.</value>
        public bool ThrowMissingOpeningTagExceptions
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets whether exceptions of type MissingClosingTagException should be thrown.
        /// </summary>
        /// <value><c>true</c> if exceptions of type MissingClosingTagException should be thrown;
        /// <c>false</c> otherwise.</value>
        public bool ThrowMissingClosingTagExceptions
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets whether exceptions of type RequiredAttributeMissingException should be thrown.
        /// </summary>
        /// <value><c>true</c> if exceptions of type RequiredAttributeMissingException should be thrown;
        /// <c>false</c> otherwise.</value>
        public bool ThrowRequiredAttributeMissingExceptions
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets whether exceptions of type UnknownAttributeValueStyleException should be thrown.
        /// </summary>
        /// <value><c>true</c> if exceptions of type UnknownAttributeValueStyleException should be thrown;
        /// <c>false</c> otherwise.</value>
        public bool ThrowUnknownAttributeValueStyleExceptions
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets whether exceptions of type RequiredAttributeValueMissingException should be thrown.
        /// </summary>
        /// <value><c>true</c> if exceptions of type RequiredAttributeValueMissingException should be thrown;
        /// <c>false</c> otherwise.</value>
        public bool ThrowRequiredAttributeValueMissingExceptions
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets whether exceptions of type NeedlessAttributeValueSpecifiedException should be thrown.
        /// </summary>
        /// <value><c>true</c> if exceptions of type NeedlessAttributeValueSpecifiedException should be thrown;
        /// <c>false</c> otherwise.</value>
        public bool ThrowNeedlessAttributeValueSpecifiedExceptions
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets whether exceptions of type MissingRuleException should be thrown.
        /// </summary>
        /// <value><c>true</c> if exceptions of type MissingRuleException should be thrown;
        /// <c>false</c> otherwise.</value>
        public bool ThrowMissingRuleExceptions
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets whether exceptions of type TagNotFoundException should be thrown.
        /// </summary>
        /// <value><c>true</c> if exceptions of type TagNotFoundException should be thrown;
        /// <c>false</c> otherwise.</value>
        public bool ThrowTagNotFoundExceptions
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets whether exceptions of type LanguageNotSupportedException should be thrown.
        /// </summary>
        /// <value><c>true</c> if exceptions of type LanguageNotSupportedException should be thrown;
        /// <c>false</c> otherwise.</value>
        public bool ThrowLanguageNotSupportedExceptions
        {
            get;
            set;
        }

        /// <summary>
        /// Initializes a new instance of the ParserOptions type.
        /// </summary>
        [LoggingAspect]
        public ParserOptions()
        {
            // Initialize fields.
            this.TokenValidatorStrategy = TokenValidatorStrategy.CaseSensitive;

            this.ThrowInvalidCharacterExceptions = true;
            this.ThrowInvalidTagExceptions = true;
            this.ThrowInvalidAttributeExceptions = true;
            this.ThrowInvalidTagNestingExceptions = true;
            this.ThrowMissingOpeningTagExceptions = true;
            this.ThrowMissingClosingTagExceptions = true;
            this.ThrowRequiredAttributeMissingExceptions = true;
            this.ThrowUnknownAttributeValueStyleExceptions = true;
            this.ThrowRequiredAttributeValueMissingExceptions = true;
            this.ThrowNeedlessAttributeValueSpecifiedExceptions = true;
            this.ThrowMissingRuleExceptions = true;
            this.ThrowTagNotFoundExceptions = true;
            this.ThrowLanguageNotSupportedExceptions = true;
        }

        /// <summary>
        /// Saves the parser options to the specified XML writer.
        /// </summary>
        /// <param name="writer">The XML writer.</param>
        [LoggingAspect]
        void IXmlWriter.Save(System.Xml.XmlWriter writer)
        {
            // Save the options.
            writer.WriteStartElement("ParserOptions");
            {
                // Save the token validator strategy.
                writer.WriteStartElement("TokenValidatorStrategy");
                {
                    writer.WriteAttributeString("Type", this.TokenValidatorStrategy.ToString());
                }
                writer.WriteEndElement();

                // Save the exceptions.
                writer.WriteStartElement("Exceptions");
                {
                    this.SaveException(writer, typeof(InvalidAttributeException), this.ThrowInvalidAttributeExceptions);
                    this.SaveException(writer, typeof(InvalidCharacterException), this.ThrowInvalidCharacterExceptions);
                    this.SaveException(writer, typeof(InvalidTagException), this.ThrowInvalidTagExceptions);
                    this.SaveException(writer, typeof(InvalidTagNestingException), this.ThrowInvalidTagNestingExceptions);
                    this.SaveException(writer, typeof(LanguageNotSupportedException), this.ThrowLanguageNotSupportedExceptions);
                    this.SaveException(writer, typeof(MissingClosingTagException), this.ThrowMissingClosingTagExceptions);
                    this.SaveException(writer, typeof(MissingOpeningTagException), this.ThrowMissingOpeningTagExceptions);
                    this.SaveException(writer, typeof(MissingRuleException), this.ThrowMissingRuleExceptions);
                    this.SaveException(writer, typeof(NeedlessAttributeValueSpecifiedException), this.ThrowNeedlessAttributeValueSpecifiedExceptions);
                    this.SaveException(writer, typeof(RequiredAttributeMissingException), this.ThrowRequiredAttributeMissingExceptions);
                    this.SaveException(writer, typeof(RequiredAttributeValueMissingException), this.ThrowRequiredAttributeValueMissingExceptions);
                    this.SaveException(writer, typeof(TagNotFoundException), this.ThrowTagNotFoundExceptions);
                    this.SaveException(writer, typeof(UnknownAttributeValueStyleException), this.ThrowUnknownAttributeValueStyleExceptions);
                }
                writer.WriteEndElement();
            }
            writer.WriteEndElement();
        }

        /// <summary>
        /// Saves the specified exception to the writer.
        /// </summary>
        /// <param name="writer">The writer.</param>
        /// <param name="exception">The type of the exception.</param>
        /// <param name="enabled"><c>true</c> if the exception is enabled; <c>false</c>
        /// otherwise.</param>
        [LoggingAspect]
        private void SaveException(XmlWriter writer, Type exception, bool enabled)
        {
            writer.WriteStartElement("Exception");
            {
                writer.WriteAttributeString("Type", exception.Name);
                writer.WriteAttributeString("Value", enabled.ToString());
            }
            writer.WriteEndElement();
        }

        /// <summary>
        /// Loads the parser options from the specified XML reader.
        /// </summary>
        /// <param name="document">The XML document.</param>
        /// <returns>The loaded parser options.</returns>
        [LoggingAspect]
        internal static ParserOptions Load(XmlDocument document)
        {
            // Create the parser options.
            ParserOptions options = new ParserOptions();

            // Get the values.
            Configuration configuration = new Configuration(document);
            options.TokenValidatorStrategy =
                (TokenValidatorStrategy)Enum.Parse(typeof(TokenValidatorStrategy),
                    configuration.Required<CodeparserNet>().Optional<ParserOptions>().Optional<TokenValidatorStrategy>().Value<Type>(options.TokenValidatorStrategy.ToString()).AsString());
            foreach (var exceptionNode in configuration.Required<CodeparserNet>().Optional<ParserOptions>().Optional<ConfigurationItems.Exceptions>().List<Exception>())
            {
                switch(exceptionNode.Value<Type>().AsString())
                {
                    case "InvalidAttributeException":
                        options.ThrowInvalidAttributeExceptions = exceptionNode.Value<Value>().AsBool().Value;
                        break;
                    case "InvalidCharacterException":
                        options.ThrowInvalidCharacterExceptions = exceptionNode.Value<Value>().AsBool().Value;
                        break;
                    case "InvalidTagException":
                        options.ThrowInvalidTagExceptions = exceptionNode.Value<Value>().AsBool().Value;
                        break;
                    case "InvalidTagNestingException":
                        options.ThrowInvalidTagNestingExceptions = exceptionNode.Value<Value>().AsBool().Value;
                        break;
                    case "InvalidLanguageNotSupportedException":
                        options.ThrowLanguageNotSupportedExceptions = exceptionNode.Value<Value>().AsBool().Value;
                        break;
                    case "InvalidMissingClosingTagException":
                        options.ThrowMissingClosingTagExceptions = exceptionNode.Value<Value>().AsBool().Value;
                        break;
                    case "InvalidMissingOpeningTagException":
                        options.ThrowMissingOpeningTagExceptions = exceptionNode.Value<Value>().AsBool().Value;
                        break;
                    case "InvalidMissingRuleException":
                        options.ThrowMissingRuleExceptions = exceptionNode.Value<Value>().AsBool().Value;
                        break;
                    case "NeedlessAttributeValueSpecifiedException":
                        options.ThrowNeedlessAttributeValueSpecifiedExceptions = exceptionNode.Value<Value>().AsBool().Value;
                        break;
                    case "RequiredAttributeMissingException":
                        options.ThrowRequiredAttributeMissingExceptions = exceptionNode.Value<Value>().AsBool().Value;
                        break;
                    case "RequiredAttributeValueMissingException":
                        options.ThrowRequiredAttributeValueMissingExceptions = exceptionNode.Value<Value>().AsBool().Value;
                        break;
                    case "TagNotFoundException":
                        options.ThrowTagNotFoundExceptions = exceptionNode.Value<Value>().AsBool().Value;
                        break;
                    case "UnknownAttributeValueStyleException":
                        options.ThrowUnknownAttributeValueStyleExceptions = exceptionNode.Value<Value>().AsBool().Value;
                        break;
                }
            }

            // Return the parser options to the caller.
            return options;
        }
    }
}